package emulator.shell.GUI.debugger;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToolBar;

import emulator.hardware.HwByte;
import emulator.hardware.HwWord;
import emulator.hardware.nmos6502.Cpu6502;
import emulator.hardware.nmos6502.Flags6502;
import emulator.util.AddressRange;

@SuppressWarnings("serial")
public class CpuRegisterPanel extends JToolBar {
	private Cpu6502 _cpu;
    private Font _textFont = new Font("Monospaced", Font.PLAIN, 12);
	private JTextField _editProgramCounter;
	private JTextField _editAccu;
	private JTextField _editX;
	private JTextField _editY;
	private JTextField _editFlags;
	private JTextField _editStackPointer;
	private JTextField _editTicks;
	private JTextField _editCounter;
	private int _lastPc = -1;
	private int _lastAccu = -1;
	private int _lastX = -1;
	private int _lastY = -1;
	private int _lastFlags = -1;
	private int _lastSp = -1;
	private long _currentCounterOffset = 0;
	private Color _defaultTextColor;
	private JButton _resetCounterButton;

	public CpuRegisterPanel(Cpu6502 cpu) 
	{
		super();
		_cpu = cpu;
		initPanel();
		updateValues(cpu.isRunning());
	}

	private void initPanel()
	{
		_editProgramCounter = createBlock("PC", "$MMMM", "Program counter");
		addSeparator();
		_editAccu = createBlock("AC", "$MM", "Accumulator");
		_editX = createBlock("X", "$MM", "Index register X");
		_editY = createBlock("Y", "$MM", "Index register Y");
		addSeparator();
		_editFlags = createBlock("Flags", "MMMMMMMM",
				"<html><b>Processor flags.</b><br><br>In order to set flags, enter the corresponding letters in any order. Clear a flag, by deleting its letter." +
				"<br><br><b><i>Flags:</b></i><dl>" +
				"<dt><b>B</b>reak</dt><dd>Indicates that an interrupt was triggered by a BRK</dd>" +
				"<dt><b>C</b>arry</dt><dd>Last operation overflowed to carry</dd>" +
				"<dt><b>D</b>ecimal</dt><dd>Enables decimal mode if set</dd>" +
				"<dt><b>I</b>nterrupt</dt><dd>Disables interrupts if set</dd>" +
				"<dt><b>N</b>egative</dt><dd>Last arithmetic operation had a negative result</dd>" +
				"<dt>O<b>v</b>erflow</dt><dd>Last arithmetic operation had a signed overflow</dd>" +
				"<dt><b>Z</b>ero</dt><dd>Last operation result was zero</dd>" +
				"</dl></html>");
		addSeparator();
		_editStackPointer = createBlock("SP", "$MM", "Stack pointer");
		addSeparator();
		_editTicks = createBlock("Cycle", "88888888", "Current clock cycle");
		_editTicks.setFocusable(false);
		
		JLabel label = new JLabel("Count");
		_editCounter = new JTextField("88888888");
		_editCounter.setFont(_textFont);
		if ("Resettable cycle counter" != null)
			_editCounter.setToolTipText("Resettable cycle counter");
		_editCounter.setFocusable(false);
		_resetCounterButton = new JButton();
		_resetCounterButton.setToolTipText("Reset cycle counter");
		_resetCounterButton.addActionListener(new ResetCycleCounter());
		_resetCounterButton.setText("0");
		add(_resetCounterButton);
		JPanel panel = new JPanel();
		panel.setLayout(new BorderLayout());
		panel.add(label, BorderLayout.NORTH);
		panel.add(_editCounter, BorderLayout.CENTER);
		panel.add(_resetCounterButton, BorderLayout.EAST);
		add(panel);
		
		addSeparator();
		_defaultTextColor = _editProgramCounter.getForeground();
	}

	private JTextField createBlock(String title, String init, String tooltip)
	{
		JLabel label = new JLabel(title);
		JTextField edit = new JTextField(init);
		edit.setFont(_textFont);
		if (tooltip != null)
			edit.setToolTipText(tooltip);
		JPanel panel = new JPanel();
		panel.setLayout(new BorderLayout());
		panel.add(label, BorderLayout.NORTH);
		panel.add(edit, BorderLayout.CENTER);
		add(panel);
		return edit;
	}

	public void updateValues(boolean runningState)
	{
		if (runningState)
		{
			updateText(_editProgramCounter, "????", false, false);
			updateText(_editAccu, "??", false, false);
			updateText(_editX, "??", false, false);
			updateText(_editY, "??", false, false);
			updateText(_editFlags, "????????", false, false);
			updateText(_editStackPointer, "??", false, false);
			updateText(_editTicks, "???????", false, false);
			updateText(_editCounter, "???????", false, false);
			_resetCounterButton.setEnabled(false);
		}
		else
		{
			int pc = _cpu.getPC();
			int accu = (int) _cpu.getAccu().getNumber();
			int x = (int) _cpu.getX().getNumber();
			int y = (int) _cpu.getY().getNumber();
			int flags = (int) _cpu.getFlags().getNumber();
			int sp = (int) _cpu.getSP().getNumber();
			updateText(_editProgramCounter, "$"+new HwWord(_cpu.getPC()).toString(), true, pc != _lastPc);
			updateText(_editAccu, "$"+_cpu.getAccu().toString(), true, accu != _lastAccu);
			updateText(_editX, "$"+_cpu.getX().toString(), true, x != _lastX);
			updateText(_editY, "$"+_cpu.getY().toString(), true, y != _lastY);
			updateText(_editFlags, _cpu.getFlags().toFlagString(), true, flags != _lastFlags);
			updateText(_editStackPointer, "$"+_cpu.getSP().toString(), true, sp != _lastSp);
			updateText(_editTicks, ""+_cpu.getTicks(), true, false);
			updateText(_editCounter, "" + (_cpu.getTicks() - _currentCounterOffset), true, false);
			_lastPc = pc;
			_lastAccu = accu;
			_lastX = x;
			_lastY = y;
			_lastFlags = flags;
			_lastSp = sp;
			_resetCounterButton.setEnabled(true);
		}
	}

	public void transferValuesToCPU()
	{
		if (!_cpu.isRunning())
		{
			_lastPc = retrieveNumber(_editProgramCounter);
			if (_lastPc >= 0)
				_cpu.setPC(_lastPc);
			else
				_lastPc = _cpu.getPC(); 
			_lastAccu = retrieveNumber(_editAccu);
			if (_lastAccu >= 0)
				_cpu.setAccu(new HwByte(_lastAccu));
			else
				_lastAccu = (int) _cpu.getAccu().getNumber();
			_lastX = retrieveNumber(_editX);
			if (_lastX >= 0)
				_cpu.setX(new HwByte(_lastX));
			else
				_lastX = (int) _cpu.getX().getNumber();
			_lastY = retrieveNumber(_editY);
			if (_lastY >= 0)
				_cpu.setY(new HwByte(_lastY));
			else
				_lastY = (int) _cpu.getY().getNumber();
			Flags6502 flags = new Flags6502();
			flags.fromFlagString(_editFlags.getText());
			_lastFlags = (int) flags.getNumber();
			_cpu.setFlags(flags);
			_lastSp = retrieveNumber(_editStackPointer);
			if (_lastSp >= 0)
				_cpu.setSP(new HwByte(_lastSp));
			else
				_lastSp = (int) _cpu.getSP().getNumber();
		}
	}
	
	private void updateText(JTextField edit, String text, boolean is_editable, boolean has_changed)
	{
		edit.setText(text);
		edit.setEditable(is_editable);
		edit.setForeground(has_changed ? Color.red : _defaultTextColor);
	}
	
	private int retrieveNumber(JTextField edit) {
		String number_text = edit.getText();
		int number = -1;
		try { number = AddressRange.addressFromString(number_text); }
		catch (NumberFormatException e)	{ }
		return number;
	}

	public class ResetCycleCounter implements ActionListener
	{
		@Override
		public void actionPerformed(ActionEvent e) 
		{
			_currentCounterOffset = _cpu.getTicks();
			_editCounter.setText("0");
		}
	}
}
